﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using JetBrains.Annotations;

namespace OpenWaves
{
    public static class StringExtensions
    {
        private const string Ellipsis = "…";

        public static string Truncate(this string value, int maxLength, TruncateOptions options = TruncateOptions.None)
        {
            Contract.Requires<ArgumentOutOfRangeException>(maxLength >= 0);

            if (value == null)
                return String.Empty;

            if (value.Length <= maxLength)
                return value;

            string truncatedValue;

            if (options.Have(TruncateOptions.WordBoundry))
            {
                var lastIndexOfSpace = value.LastIndexOf(' ', maxLength);

                truncatedValue = value.Substring(0, lastIndexOfSpace > 0 ? lastIndexOfSpace : maxLength);
            }
            else
            {
                truncatedValue = value.Substring(0, maxLength);
            }

            if (options.Have(TruncateOptions.PreserveTrailingWhiteSpace) == false)
                truncatedValue=truncatedValue.TrimEnd();

            if (options.Have(TruncateOptions.AddEllipsis))
                truncatedValue += Ellipsis;

            return truncatedValue;
        }

        public static IEnumerable<int> FindAll(this string source, string value)
        {
            return FindAll(source, value, StringComparison.Ordinal);
        }

        public static IEnumerable<int> FindAll(this string source, string value, StringComparison comparisonType)
        {
            Contract.Requires<ArgumentNullException>(source != null);
            Contract.Requires<ArgumentNullException>(String.IsNullOrEmpty(value) == false);
            Contract.Ensures(Contract.Result<IEnumerable<int>>() != null);

            var last = 0;
            var result = new List<int>();
            while (true)
            {
                int index = source.IndexOf(value, last, comparisonType);
                if (index == -1)
                {
                    break;
                }
                result.Add(index);
                last = index + 1;
            }
            return result;
        }

        public static string Capitalize(this string value)
        {
            Contract.Requires<ArgumentNullException>(value != null);
            Contract.Ensures(Contract.Result<string>() != null);

            if (value.Length == 0)
                return value;

            if (value.Length == 1)
                return value.Substring(0, 1).ToUpper(CultureInfo.InvariantCulture);

            return value.Substring(0, 1).ToUpper(CultureInfo.InvariantCulture) + value.Substring(1);
        }

        public static string SplitCamelCase(this string input)
        {
            Contract.Requires(string.IsNullOrEmpty(input) == false);

            var result = Regex.Replace(input, @"\B([A-Z][a-z])", " $1");
            result = Regex.Replace(result, @"\B([^\d])(\d)", "$1 $2");
            return result.Trim();
        }

        public static bool IsNullOrWhiteSpace(this string input)
        {
            #if NET_40_OR_GREATER
                return string.IsNullOrWhiteSpace(input);
            #else
                return string.IsNullOrEmpty(input) || input.Trim().Length == 0;
            #endif
        }

        public static string Truncate(this string value, int maxLength, ICharacterWidthStatisticsProvider characterWidthStatisticsProvider)
        {
            return value.Truncate(maxLength, characterWidthStatisticsProvider, TruncateOptions.None);
        }

        public static string Truncate(this string value, int maxLength, ICharacterWidthStatisticsProvider characterWidthStatisticsProvider, TruncateOptions options)
        {
            Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(value));
            Contract.Requires<ArgumentException>(maxLength>0);

            if (options.Have(TruncateOptions.WordBoundry) || options.Have(TruncateOptions.PreserveTrailingWhiteSpace))
            {
                throw new NotSupportedException(TruncateOptions.WordBoundry + " and " + TruncateOptions.PreserveTrailingWhiteSpace + "are not supported");
            }

            var i = 0;
            var length = 0.0;
            var b = new StringBuilder(Math.Min(maxLength, value.Length));
            while (length < maxLength && i < value.Length)
            {
                b.Append(value[i]);
                length += characterWidthStatisticsProvider.GetCharacterWidth(value[i]);
                i++;
            }

            if (b.Length == value.Length)
                return value;

            if (options.Have(TruncateOptions.AddEllipsis))
                b.Append(Ellipsis);

            return b.ToString();
        }

        [StringFormatMethod("format")]
        public static string FormatWith(this string format, params object[] args)
        {
            return String.Format(format, args);
        }
    }

    [Flags]
    public enum TruncateOptions
    {
        None = 0,
        AddEllipsis = 1,
        WordBoundry = 2,
        PreserveTrailingWhiteSpace = 4
    }

    static class TruncateOptionsExtensions
    {
        public static bool Have(this TruncateOptions options, TruncateOptions flags)
        {
            return (options & flags) == flags;
        }
    }

}
